package com.diandian.backend.kuaishou.life.exchange.service;

import com.alibaba.fastjson.JSON;
import com.diandian.backend.kuaishou.life.exchange.dto.KuaishouLifeTokenAddParamDTO;
import com.diandian.backend.kuaishou.life.exchange.dto.KuaishouLifeTokenDetailParamDTO;
import com.diandian.backend.kuaishou.life.exchange.resp.KuaishouLifeTokenDetailResp;
import com.diandian.base.boot.starter.exception.DefaultException;
import com.diandian.kuaishou.life.boot.starter.constants.KuaiShouLifeConstant;
import com.diandian.kuaishou.life.boot.starter.dto.GetTokenByCodeRespDTO;
import com.diandian.kuaishou.life.boot.starter.dto.RefreshTokenRespDTO;
import com.diandian.kuaishou.life.boot.starter.service.KuaiShouLifeTokenBasicService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Calendar;
import java.util.Date;

@Service
public class BusinessTokenGetService {

    private static final Logger logger = LoggerFactory.getLogger(BusinessTokenGetService.class);

    @Autowired
    private KuaiShouLifeTokenBasicService kuaiShouLifeTokenBasicService;

    @Autowired
    private BusinessKuaishouLifeTokenService businessKuaishouLifeTokenService;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 获取token
     * @param appKey 应用key
     * @param merchantId 商户id
     * @return accessToken
     */
    @Transactional(rollbackFor = Exception.class)
    public String getToken(String appKey, String appSecret, String merchantId){

        // 1. redis中的token信息
        // 1.1 从redis中获取token
        String redisKey = KuaiShouLifeConstant.KUAI_SHOU_TOKEN_PRIFIX + appKey + ":" + merchantId;
        Object tokenObject = redisTemplate.opsForValue().get(redisKey);

        // 1.2 redis中有，校验是否有效
        if(tokenObject != null){
            logger.info("缓存中找到 AppKey {} 商户 {} 的token信息：{}", appKey, merchantId, JSON.toJSONString(tokenObject));

            // 1.2.1 有效，使用
            GetTokenByCodeRespDTO tokenDTO = JSON.parseObject(tokenObject.toString(), GetTokenByCodeRespDTO.class);
            if(tokenDTO.getAccess_token() != null && !"".equals(tokenDTO.getAccess_token()) && tokenDTO.getExpired_date() != null && tokenDTO.getExpired_date().after(new Date())){
                logger.info("当前access token有效，直接返回。");
                return tokenDTO.getAccess_token();
            }
            logger.info("当前access token无效，尝试使用refresh token刷新access token...");

            // 1.2.2 无效，使用refreshToken获取accessToken并更新redis和数据库
            if(tokenDTO.getRefresh_token() != null && !"".equals(tokenDTO.getRefresh_token()) && tokenDTO.getExpiry_date_refresh_token() != null && tokenDTO.getExpiry_date_refresh_token().after(new Date())){
                logger.info("refresh token依旧有效，将使用refreshToken更新token");
                return updateLocalToken(appKey, appSecret, merchantId, tokenDTO.getRefresh_token());
            }
            logger.info("refresh token无效，抛出异常。");
            throw new DefaultException("当前本地无有效的access token和refresh token信息，请重新授权门店。");
        }

        // 2. 数据库中的token信息
        logger.info("缓存中未找到 AppKey {} 商户 {} 的token信息，将使用数据库中的数据进行处理...", appKey, merchantId);

        KuaishouLifeTokenDetailParamDTO tokenDetailParamDTO = new KuaishouLifeTokenDetailParamDTO();
        tokenDetailParamDTO.setAppKey(appKey);
        tokenDetailParamDTO.setMerchantId(merchantId);
        tokenDetailParamDTO.setDel(0);
        KuaishouLifeTokenDetailResp detailResp = businessKuaishouLifeTokenService.getDetail(tokenDetailParamDTO);

        // 2.1 数据库中有，校验是否有效
        if(detailResp != null){
            logger.info("数据库找到AppKey {} 商户 {} 的token信息：{}", appKey, merchantId, JSON.toJSONString(detailResp));

            // 2.1.1 有效，使用
            if(detailResp.getAccessToken() != null && !"".equals(detailResp.getAccessToken()) && detailResp.getExpiresDate() != null && detailResp.getExpiresDate().after(new Date())){
                logger.info("当前access token有效，补充redis并返回。");
                // 补充到redis中
                redisTemplate.opsForValue().set(redisKey, detailResp.getSourceTokenData());
                return detailResp.getAccessToken();
            }

            logger.info("当前access token无效，尝试使用refresh token刷新access token...");

            // 2.1.2 无效，使用refreshToken获取accessToken并更新redis和数据库
            if(detailResp.getRefreshToken() != null && !"".equals(detailResp.getRefreshToken()) && detailResp.getExpiresDateRefreshToken() != null && detailResp.getExpiresDateRefreshToken().after(new Date())){
                logger.info("refresh token依旧有效，将使用refreshToken更新token");
                return updateLocalToken(appKey, appSecret, merchantId, detailResp.getRefreshToken());
            }
        }

        throw new DefaultException("当前本地无有效的access token和refresh token信息，请重新授权门店。");
    }

    private String updateLocalToken(String appKey, String appSecret, String merchantId, String refreshToken){

        // 兑换新的token信息
        RefreshTokenRespDTO refreshTokenRespDTO = kuaiShouLifeTokenBasicService.refreshToken(KuaiShouLifeConstant.KUAI_SHOU_GRANt_TYPE_DEFAULT, refreshToken, appKey, appSecret);
        if(refreshTokenRespDTO == null){
            throw new DefaultException("刷新快手access token失败");
        }
        if(!KuaiShouLifeConstant.PARAM_RESPONSE_SUCCESS_RESULT.equals(refreshTokenRespDTO.getResult())){
            throw new DefaultException("刷新快手access token失败，失败原因：" + refreshTokenRespDTO.getError_msg());
        }
        logger.info("兑换到新的token信息：{}", JSON.toJSONString(refreshTokenRespDTO));

        // 补充数据
        String accessToken = refreshTokenRespDTO.getAccess_token();
        Long expiresIn = refreshTokenRespDTO.getExpires_in();
        Date now = new Date();
        Date expriesDate = getAccessTokenExpiredDate(now, expiresIn);
        Date expiresDateRefreshToken = getRefreshTokenExpiredDate(now, KuaiShouLifeConstant.KUAI_SHOU_REFRESH_TOKEN_EXPIRE_DAY);
        refreshTokenRespDTO.setExpired_date(expriesDate);
        refreshTokenRespDTO.setExpiry_date_refresh_token(expiresDateRefreshToken);
        String dataJson = JSON.toJSONString(refreshTokenRespDTO);

        // 删除原有的token记录
        businessKuaishouLifeTokenService.deleteByAppKeyAndMerchantId(appKey, merchantId);

        // 保存入库
        KuaishouLifeTokenAddParamDTO tokenAddParamDTO = new KuaishouLifeTokenAddParamDTO();
        tokenAddParamDTO.setMerchantId(merchantId);
        tokenAddParamDTO.setAppKey(appKey);
        tokenAddParamDTO.setSourceTokenData(dataJson);
        tokenAddParamDTO.setAccessToken(accessToken);
        tokenAddParamDTO.setRefreshToken(refreshTokenRespDTO.getRefresh_token());
        tokenAddParamDTO.setExpiresIn(expiresIn);
        tokenAddParamDTO.setExpiresDate(expriesDate);
        tokenAddParamDTO.setExpiresDateRefreshToken(expiresDateRefreshToken);
        tokenAddParamDTO.setScopes(refreshTokenRespDTO.getScopes());

        logger.info("执行token入库操作，数据：{}", JSON.toJSONString(tokenAddParamDTO));
        businessKuaishouLifeTokenService.add(tokenAddParamDTO);

        // 保存到redis中
        String redisKey = KuaiShouLifeConstant.KUAI_SHOU_TOKEN_PRIFIX + appKey + ":" + merchantId;
        logger.info("执行token缓存操作，key:{}, 数据：{}", redisKey, dataJson);
        redisTemplate.opsForValue().set(redisKey, dataJson);

        logger.info("执行商家店铺token刷新处理完成。");

        return accessToken;
    }

    /**
     * 计算access token过期时间
     * @return
     */
    private Date getAccessTokenExpiredDate(Date date, Long seconds){

        if(date == null || seconds == null){
            return null;
        }

        if(seconds.intValue() <= 3){
            return date;
        }

        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.SECOND, seconds.intValue() - 3); // 默认减掉10S作为交互占用时间
        return calendar.getTime();
    }

    /**
     * 计算refresh token过期时间
     * @return
     */
    private Date getRefreshTokenExpiredDate(Date date, int days){
        if(date == null){
            return null;
        }

        if(days <= 0){
            return date;
        }

        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.DAY_OF_MONTH, days);
        return calendar.getTime();
    }
}
